package chapter_6.demo2;

import chapter_4.Dish;
import chapter_6.CaloricLevel;

import java.util.*;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.*;

/**
 * @author: hero生仔
 */
public class Solution {
    static List<Dish> menu = Arrays.asList(
            new Dish("pork", false, 800, Dish.Type.MEAT),
            new Dish("beef", false, 700, Dish.Type.MEAT),
            new Dish("chicken", false, 400, Dish.Type.MEAT),
            new Dish("french fries", true, 530, Dish.Type.OTHER),
            new Dish("rice", true, 350, Dish.Type.OTHER),
            new Dish("season fruit", true, 120, Dish.Type.OTHER),
            new Dish("pizza", true, 550, Dish.Type.OTHER),
            new Dish("prawns", false, 300, Dish.Type.FISH),
            new Dish("salmon", false, 450, Dish.Type.FISH));

    public static void main(String[] args) {
        test01();

        test02();

        test03();

        test04();

        test05();
    }

    private static void test05() {
        // 将数字按质数和非质数分区
        Map<Boolean, List<Integer>> map = new Solution().partitionPrimes(12);
        System.out.println(map);
    }

    // 创建一个方法,它接受参数int n，并将前n个自然数分为质数和非质数
    public boolean isPrime(int candidate) {
        return IntStream.range(2, candidate)
                .noneMatch(i -> candidate % i == 0);
    }

    // 为了把前n个数字分为质数和非质数，只要创建一个包含这n个数的流，用刚刚写的isPrime方法作为谓词
    public Map<Boolean, List<Integer>> partitionPrimes(int n) {
        return IntStream.rangeClosed(2, n).boxed()
                .collect(
                        partitioningBy(candidate -> isPrime(candidate)));
    }

    private static void test04() {
        // 上面说的都是分组的,这里开始说分区:由true或者false 作为key组成的map
        Map<Boolean, List<Dish>> map = menu.stream()
                .collect(partitioningBy(Dish::isVegetarian));

        // 就可以找出所有的素食菜肴了
        List<Dish> dishesTrue = map.get(true);
        System.out.println(dishesTrue);
        // 同理
        List<Dish> dishesFalse = map.get(false);
        System.out.println(dishesFalse);

        // 分组的那个收集器还可以传递第二个收集器
        Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType =
                menu.stream().collect(
                        partitioningBy(Dish::isVegetarian,
                                groupingBy(Dish::getType)));
        System.out.println(vegetarianDishesByType);

        // 找到素食和非素食中热量最高的菜
        Map<Boolean, Dish> map1 = menu.stream().collect(
                partitioningBy(Dish::isVegetarian,
                        // 转换函数,转换收集器
                        collectingAndThen(
                                maxBy(Comparator.comparing(Dish::getCalories)),
                                // 将 Optional 类型的进行转换
                                Optional::get
                        )
                )
        );
        System.out.println(map1);
    }

    private static void test03() {
    /*
    由于 test02 中同意 maxBy 方法生成了一个 值是 Optional 类型的,那么这个类型一定是有值的
   ,不然不会出现.
    把收集器的结果转换为另一种类型
    因为分组操作的Map结果中的每个值上包装的Optional没什么用，所以你可能想要把它们去掉。
     */
        Map<Dish.Type, Dish> map = menu.stream()
                .collect(
                        groupingBy(Dish::getType,
                                // 要转换的收集器以及转换函数，并返回另一个收集器
                                collectingAndThen(
                                        maxBy(Comparator.comparing(Dish::getCalories)),
                                        Optional::get)));
        System.out.println(map);

        /*
        上面的那个例子是 groupBy 收集器和collectingAndThen收集器联合使用的例子
        这里还有其他的:summingInt & mapping
         */
        Map<Dish.Type, IntSummaryStatistics> map1 = menu.stream()
                .collect(groupingBy(Dish::getType,
                        summarizingInt(Dish::getCalories)));
        System.out.println(map1);

        /*
        常常和groupingBy 联合使用的另一个收集器是 mapping 方法生成
        这个方法接受两个参数：一个函数对流中的元素做变换，另一个则将变换的结果对象收集起来。
         举个例子:对于每种类型的Dish，菜单中都有哪些CaloricLevel
         */
        Map<Dish.Type, Set<CaloricLevel>> map3 = menu.stream()
                .collect(groupingBy(Dish::getType,
                        mapping(
                                dish -> {
                                    if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                                    else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                                    else return CaloricLevel.FAT;
                                },
                                // 生成的 CaloricLevel 流传递给一个 toSet 收集器
                                toSet())));
        System.out.println(map3);

        // 假如我对map3更加优化:
        Map<Dish.Type, HashSet<CaloricLevel>> map4 = menu.stream()
                .collect(groupingBy(Dish::getType,
                        mapping(
                                dish -> {
                                    if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                                    else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                                    else return CaloricLevel.FAT;
                                },
                                // 生成的 CaloricLevel 流传递给一个 toSet 收集器
                                toCollection(HashSet::new))));
        System.out.println(map4);
    }

    private static void test02() {
        // 按照子组收集数据:每一种类型的餐食有多少个
        Map<Dish.Type, Long> map = menu.stream()
                .collect(groupingBy(Dish::getType, counting()));
        System.out.println(map);

        // 按照子组收集数据:最高热量的那个餐食是啥
        Map<Dish.Type, Optional<Dish>> map1 = menu.stream()
                .collect(groupingBy(Dish::getType,
                        maxBy(Comparator.comparing(Dish::getCalories))));
        System.out.println(map1);
    }

    private static void test01() {
        // 先按照餐食的类型进行分类,然后再按照热量进行分类(二级分类的使用)
        Map<Dish.Type, Map<CaloricLevel, List<Dish>>> map = menu.stream()
                .collect(groupingBy(Dish::getType,
                        groupingBy(dish -> {
                            if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                            else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                            else return CaloricLevel.FAT;
                        })));
        System.out.println(map);
    }
}
